package
{
	import flash.geom.Point;
	import flash.net.SharedObject;
	import flash.utils.Dictionary;
	
	import org.flixel.*;
	
	public class TutorialPlayingState extends FlxState
	{
		//Cookie
		protected var localInformation:SharedObject;
		
		//Music and Mute Button.
		protected var music:String;
		protected var playIt:Boolean;
		protected var musicIcon:Music;
		protected var replayIco:FlxSprite;
		
		// Game sizing constants
		public static const CELL_SIZE:int = 46;
		public static const ROWS:int = 11;
		public static const COLS:int = 11;
		public static const X_OFFSET:int = 5;
		public static const Y_OFFSET:int = 5;
		
		// includes the bounding bars that contribute to the offset.
		public static const GRID_WIDTH:int = (CELL_SIZE * COLS) + (2 * X_OFFSET);
		public static const GRID_HEIGHT:int = (CELL_SIZE * ROWS) + (2 * Y_OFFSET);
		
		// Index -> item, row major order for indices
		protected var tileMap:Dictionary;
		protected var arrowMap:Dictionary;
		protected var Ground:FlxGroup;
		protected var Blocks:FlxGroup;
		protected var Tiles:FlxGroup;
		protected var Sources:FlxGroup;
		protected var UnlitSources:FlxGroup;
		protected var SourceLightAnims:FlxGroup;
		
		protected var Forges:FlxGroup;
		protected var TransformSprites:FlxGroup;
		protected var Beams:FlxGroup;
		protected var obsImages:FlxGroup;
		protected var bin:ArrowBin;
		protected var arrowGroup:FlxGroup;
		protected var player:Player = null;
		protected var playerImageSprite:FlxSprite = null;
		protected var train:Train = null;
		protected var collideTrain:FlxGroup = null;
		protected var obs:FlxGroup;
		protected var Monsters:FlxGroup;
		
		protected var playerLastX:int;
		protected var playerLastY:int;
		
		//beam sound control:
		protected var arrowLastPlaced:int = -1;
		
		//loggin
		private var log:Logging = Elemental.Log;
		
		//Lock
		protected var MonsterLock:Boolean;
		
		protected var cheatSheetOpen:Boolean = false;
		protected var Complete:Boolean;
		
		protected var LevelCompleteDelay:int;
		
		protected var CurrentMap:int
		
		protected var pid:String;
		
		protected static const MAPONE:String = "0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,27,0,26,0,25,0,24,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,23,0,22,0,21,0,20,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,99,0,0,0,0,0";
		
		protected static const MAPTWO:String = "0,0,1,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,1,0,1,0,0,0,0,0,0,0,0,1,0,1,0,0,0,0,0,0,0,27,0,0,1,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,1,0,23,0,0,0,0,0,0,0,0,1,99,0,0,0,0,0";
		
		protected static const MAPTHR:String = "0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,100,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,26,0,1,0,0,0,0,22,0,0,0,0,0,1,99,0,0,0,0,0";
		
		protected static const MAPFOU:String = "0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,21,0,0,0,0,0,0,0,25,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,52,1,1,1,1,1,0,22,0,0,0,100,0,0,0,20,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,99,0,0,0,0,0";
		
		protected static const MAPFIV:String = "71,0,0,0,70,0,0,0,72,0,0,0,0,0,0,0,0,0,0,0,0,0,0,21,0,0,0,0,0,0,0,25,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,52,1,1,1,1,1,0,22,0,0,0,100,0,0,0,20,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,99,0,0,0,0,0";
		
		
		
		protected static const ARRONE:String = "4,0,0,0";
		protected static const ARRTWO:String = "1,1,2,0";
		protected static const ARRTHR:String = "1,0,0,0";
		protected static const ARRFOU:String = "0,0,1,1";
		protected static const ARRFIV:String = "0,0,1,1";
		
		protected static const CHIONE:String = "";
		protected static const CHITWO:String = "";
		protected static const CHITHR:String = "";
		protected static const CHIFOU:String = "";
		protected static const CHIFIV:String = "3";
		
		//The Tutorials
		protected var tutorialAnimations:TutorialAnimations;
		protected var showingTutorial:Boolean;
		
		
		public function TutorialPlayingState(pid:String, level:int)
		{
			this.pid = pid;
//			var map:String = mapSpec.getMap();
//			var ArrowArray:Array = new Array();
//			var trainString:Array = new Array();
//			var tmpArrows:Array = mapSpec.getArrows();
//			var tmpChicks:Array = mapSpec.getChickens();
//			for (var t:int = 0; t < tmpArrows.length; t++)
//				ArrowArray.push(tmpArrows[t]);
//			for (var u:int = 0; u < tmpChicks.length; u++)
//				trainString.push(tmpChicks[u]);
//			
//			var levelID:int = mapSpec.getMapID();
			this.add(new FlxSprite(0,0, Res.bg));
			CurrentMap = level;
			LevelCompleteDelay = 100;
			
			//Initiate music and player options to turn it off.
			localInformation = SharedObject.getLocal("elementalGameInfo");
			
			music = Res.gokun;//gokun;
			
			replayIco = new FlxSprite(642, 0, Res.replay);
			
			musicIcon = new Music(676,0);
			
			this.add(musicIcon);
			this.add(replayIco);
			
			//Initialize Flx Groups
			Ground = new FlxGroup();
			Blocks = new FlxGroup();
			Tiles = new FlxGroup();
			Sources = new FlxGroup();
			UnlitSources = new FlxGroup();
			SourceLightAnims = new FlxGroup();
			Forges = new FlxGroup();
			TransformSprites = new FlxGroup();
			Beams = new FlxGroup();
			obsImages = new FlxGroup();
			obs = new FlxGroup();
			collideTrain = new FlxGroup();
			Monsters = new FlxGroup();
			arrowGroup = new FlxGroup();
			
			
			tileMap = new Dictionary();
			arrowMap = new Dictionary();
			
			var mapStr:String = "";
			var arrowsArray:Array = new Array();
			var chickens:int = 0;
			if (level == 0) {
				mapStr = MAPONE;
				arrowsArray = ARRONE.split(",");
			} else if (level == 1) {
				mapStr = MAPTWO;
				arrowsArray = ARRTWO.split(",");
			} else if (level == 2) {
				mapStr = MAPTHR;
				arrowsArray = ARRTHR.split(",");
			} else if (level == 3) {
				mapStr = MAPFOU;
				arrowsArray = ARRFOU.split(",");
			} else if (level == 4) {
				mapStr = MAPFIV;
				arrowsArray = ARRFIV.split(",");
				chickens = int(CHIFIV);
			}
			
			var mapArray:Array = mapStr.split(",");
			
			var tBound:FlxTileblock = new FlxTileblock(0, 0, GRID_WIDTH, Y_OFFSET);
			var bBound:FlxTileblock = new FlxTileblock(0, GRID_HEIGHT - Y_OFFSET, GRID_WIDTH, Y_OFFSET);
			var lBound:FlxTileblock = new FlxTileblock(0, 0, X_OFFSET, GRID_HEIGHT);
			var rBound:FlxTileblock = new FlxTileblock(GRID_WIDTH - X_OFFSET, 0, X_OFFSET, GRID_HEIGHT);
			tBound.loadTiles(Res.topBoundImage, GRID_WIDTH,Y_OFFSET, 0);
			bBound.loadTiles(Res.bottomBoundImage, GRID_WIDTH,Y_OFFSET, 0);
			lBound.loadTiles(Res.leftBoundImage, X_OFFSET, GRID_HEIGHT, 0);
			rBound.loadTiles(Res.rightBoundImage, X_OFFSET, GRID_HEIGHT, 0);
			Blocks.add(tBound);
			Blocks.add(lBound);
			Blocks.add(rBound);
			Blocks.add(bBound);
			
			//Parse and create map
			for (var i:int = 0; i < ROWS; i++) {
				for (var j:int = 0; j < COLS; j++) {
					var index:int = (i*COLS) + j;
					var pos:Point = getPointFromIndex(index);
					var g:FlxTileblock;
					g = new FlxTileblock(pos.x, pos.y, CELL_SIZE, CELL_SIZE);
					g.loadTiles(Res.grassTiles, CELL_SIZE, CELL_SIZE, 0);
					Ground.add(g);
					if (mapArray[index] == "1" || mapArray[index] == "2" || mapArray[index] == "3") {
						var b:FlxTileblock;
						b = new FlxTileblock(pos.x, pos.y, CELL_SIZE,CELL_SIZE);
						if (mapArray[index] == "1") {
							b.loadTiles(Res.grassWalls, CELL_SIZE, CELL_SIZE, 0);
						} else if (mapArray[index] == "2") {
							b.loadTiles(Res.barrenWalls, CELL_SIZE, CELL_SIZE, 0);
						} else {
							b.loadTiles(Res.voidWalls, CELL_SIZE, CELL_SIZE, 0);
						}
						Blocks.add(b);
						tileMap[index] = b;
					} else if (mapArray[index] == "100") {
						var sF:SourceForge
						var tranAnim:FlxSprite = new FlxSprite(pos.x - 23, pos.y - 26, Res.blankImage);
						TransformSprites.add(tranAnim);
						sF = new SourceForge(pos.x, pos.y, tranAnim);
						Tiles.add(sF);
						Forges.add(sF);
						tileMap[index] = sF;
					} else if (mapArray[index] == "20") {
						var lightAnim0:FlxSprite = new FlxSprite(pos.x - 23, pos.y - 26, Res.blankImage);
						SourceLightAnims.add(lightAnim0);
						var ws:WaterSource = new WaterSource(pos.x, pos.y, lightAnim0, true);
						ws.turnOn();
						Sources.add(ws);
						tileMap[index] = ws;
					} else if (mapArray[index] == "21") {
						var lightAnim1:FlxSprite = new FlxSprite(pos.x - 23, pos.y - 26, Res.blankImage);
						SourceLightAnims.add(lightAnim1);
						var wis:WindSource = new WindSource(pos.x, pos.y, lightAnim1);
						wis.turnOn();
						Sources.add(wis);
						tileMap[index] = wis;
					} else if (mapArray[index] == "22") {
						var lightAnim2:FlxSprite = new FlxSprite(pos.x - 23, pos.y - 26, Res.blankImage);
						SourceLightAnims.add(lightAnim2);
						var es:EarthSource = new EarthSource(pos.x, pos.y, lightAnim2);
						es.turnOn();
						Sources.add(es);
						tileMap[index] = es;
					} else if (mapArray[index] == "23") {
						var lightAnim3:FlxSprite = new FlxSprite(pos.x - 23, pos.y - 26, Res.blankImage);
						SourceLightAnims.add(lightAnim3);
						var ls:LightSource = new LightSource(pos.x, pos.y, lightAnim3);
						ls.turnOn();
						Sources.add(ls);
						tileMap[index] = ls;
					} else if (mapArray[index] == "24" || mapArray[index] == "28") {
						var lightAnim4:FlxSprite = new FlxSprite(pos.x - 23, pos.y - 26, Res.blankImage);
						SourceLightAnims.add(lightAnim4);
						var uws:WaterSource = new WaterSource(pos.x, pos.y, lightAnim4);
						UnlitSources.add(uws);
						tileMap[index] = uws;
						if (mapArray[index] == "28") {
							uws.turnOn();
						}
					} else if (mapArray[index] == "25" || mapArray[index] == "29") {
						var lightAnim5:FlxSprite = new FlxSprite(pos.x - 23, pos.y - 26, Res.blankImage);
						SourceLightAnims.add(lightAnim5);
						var uwis:WindSource = new WindSource(pos.x, pos.y, lightAnim5);
						UnlitSources.add(uwis);
						tileMap[index] = uwis;
						if (mapArray[index] == "29") {
							uwis.turnOn();
						}
					} else if (mapArray[index] == "26" || mapArray[index] == "30") {
						var lightAnim6:FlxSprite = new FlxSprite(pos.x - 23, pos.y - 26, Res.blankImage);
						SourceLightAnims.add(lightAnim6);
						var ues:EarthSource = new EarthSource(pos.x, pos.y,lightAnim6);
						UnlitSources.add(ues);
						tileMap[index] = ues;
						if (mapArray[index] == "30") {
							ues.turnOn();
						}
					} else if (mapArray[index] == "27" || mapArray[index] == "31") {
						var lightAnim7:FlxSprite = new FlxSprite(pos.x - 23, pos.y - 26, Res.blankImage);
						SourceLightAnims.add(lightAnim7);
						var uls:LightSource = new LightSource(pos.x, pos.y,lightAnim7);
						UnlitSources.add(uls);
						tileMap[index] = uls;
						if (mapArray[index] == "31") {
							uls.turnOn();
						}
					} else if (mapArray[index] == "99") {
						playerImageSprite = new FlxSprite(j*CELL_SIZE + X_OFFSET, i*CELL_SIZE + Y_OFFSET, null);
						player = new Player(j*CELL_SIZE + X_OFFSET, i*CELL_SIZE + Y_OFFSET, playerImageSprite);
						tileMap[index] = "player";
					} else if (int(mapArray[index]) >= 50 && int(mapArray[index]) <= 69){
						if (int(mapArray[index]) == 56) {
							var pit:FlxTileblock;
							pit = new FlxTileblock(pos.x, pos.y, CELL_SIZE,CELL_SIZE);
							pit.loadTiles(Res.pitImages, 46, 46, 0);
							Ground.add(pit);
						}
						var type:int = int(mapArray[index])-50;
						var obsImage:FlxSprite = new FlxSprite(pos.x, pos.y, null);
						var o:Obstacle = new Obstacle(pos.x, pos.y, type, obsImage);
						
						obsImages.add(obsImage);
						obs.add(o);
						//Allows beams to travel thrugh obstacles and didn't look like
						//we were using tilemap for anything else so...
						tileMap[index] = o;
					} else if (int(mapArray[index]) >= 70 && int(mapArray[index]) <= 90) {
						var monster:Monster = new Monster(new Point(pos.x, pos.y), int(mapArray[index]) - 70);
						Monsters.add(monster);
					}
				}
			}
			
			//Construct the Train
			train = new Train(player);
			for (var k:int = 0; k < chickens; k++) {
				var chicken:Chicken = new Chicken(player.x, player.y, null);
				train.addAnimal(chicken);
				collideTrain.add(chicken);
			}
			
			this.add(Ground);
			this.add(obs);
			this.add(obsImages);
			this.add(Blocks);
			this.add(Tiles);
			this.add(Forges);
			this.add(SourceLightAnims);
			this.add(Sources);
			this.add(UnlitSources);
			this.add(Beams);
			this.add(arrowGroup);
			//beams and arrows
			bin = new ArrowBin(GRID_WIDTH, 0, 4, 1, arrowsArray);
			this.add(bin);
			
			this.add(collideTrain);
			this.add(TransformSprites);
			this.add(player);
			this.add(playerImageSprite);
			this.add(Monsters);
			
			Complete = false;
			this.add(new FlxSprite(253, 526, Res.tootTitleThing));
			var t1:FlxText = new FlxText(520, 190, 176, "Press R - to restart", false);
			t1.setFormat("Courier New", 18, 0x000000, "center");
			this.add(t1);
			
			tutorialAnimations = new TutorialAnimations(CurrentMap+1);
			displayTutorialAnimation();	
			
			log.startDquest(Logging.TUTORIAL+CurrentMap);
		}
		
		public override function update():void {
			super.update();
			
			Logging.time += FlxG.elapsed;
			
			if (showingTutorial) {
				if (tutorialAnimations.over()) {
					showingTutorial = false;
					remove(tutorialAnimations, true);
					Elemental.FREEZE = false;
				}
			} else {
					
				if (FlxG.keys.R) {
					FlxG.switchState(new TutorialPlayingState(pid, CurrentMap));
				}
				
				//Collisions to calculate.
				MonsterLock = true;
				train.updateTrain();
				
				FlxG.overlap(obs,player,collideObs);
				FlxG.overlap(Forges, player, transform);
				FlxG.overlap(UnlitSources, player, lightSource);
				FlxG.collide(Blocks, player);
				FlxG.collide(Blocks, collideTrain, returnAnimal);
				FlxG.collide(Monsters, player, gameOver);
				if (player.element != Elemental.VOID) {
					FlxG.collide(Monsters, Blocks, monsterAI);
					FlxG.collide(Monsters, obs, monsterAI);
					FlxG.collide(Monsters, collideTrain, killMonster);
				}
				
				if (!Complete) {
					if (UnlitSources.getFirstExtant() == null) {
						if (LevelCompleteDelay > 0) {
							LevelCompleteDelay--;
							Elemental.FREEZE = true;
						} else {
							Complete = true;
							log.logLevelClear(String(Logging.TUTORIAL+CurrentMap));
						}
					}
				}
				if (player.element == Elemental.VOID) {
					var monsterIndex:int = Monsters.members.length;
					for (var m:int = 0; m < monsterIndex; m++) {
						var aMonster:Monster = Monsters.members[m];
						if (aMonster != null) {
							aMonster.toPlayer(new Point(player.x, player.y));
						}
					}
					if (collideTrain.getFirstExtant() != null) {
						for (var b:int = 0; b < 5; b++) {
							if (collideTrain.getFirstExtant != null) {
								collideTrain.remove(collideTrain.getFirstExtant());
							}
						}
						train.removeAll();
					}
					FlxG.overlap(player, Sources, turnOffSource);
				}
				//			if (RightArrowSprite != null) {
				
				//				if (FlxG.mouse.justPressed()) {
				//					if (!cheatSheetOpen) {
				//						this.remove(RightArrowSprite);
				//						RightArrowSprite = null;
				//					}
				//				} else {
				//					if (cheatSheetOpen) {
				//						ArrowCountDown = 500;
				//					} else if (ArrowCountDown == 0) {
				//						this.remove(RightArrowSprite);
				//						RightArrowSprite = null;
				//					} else {
				//						ArrowCountDown--;
				//					}
				//				}
				//			}
				//			
				if (Complete) {
					//log.logLevelClear(String(Logging.TUTORIAL+CurrentMap));
					
					Elemental.FREEZE = false;
					Client.updatePlayerStat(pid,"tutorial", 1);
					
					if (CurrentMap == 4) {
						FlxG.switchState(new FacebookPreloader(true));
					} else {
						FlxG.switchState(new TutorialPlayingState(pid, CurrentMap + 1));		
					}
				}
				if (FlxG.mouse.justPressed()) {
					clearBeams();
					var index:int;
					index = getIndexFromPoint(player.x+player.width/2, player.y+player.height/2);
					if (arrowMap[index] == null && bin.overlapsPoint(FlxG.mouse.screenX, FlxG.mouse.screenY) && !(tileMap[index] is Obstacle)){
						var arrow:Arrow = bin.dispatchArrow(index);
						//log place arrow here
						if (arrow != null) {
							arrowGroup.add(arrow);
							arrowMap[index] = arrow;
							log.logArrowPlaced(index,arrow.getOrientation());
							this.arrowLastPlaced = index;
						}
					} else {
						index = getIndexFromPoint(FlxG.mouse.screenX, FlxG.mouse.screenY);
						if (arrowMap[index] != null){
							//arrowMap[index] == null;
							arrowGroup = bin.reclaimArrow(arrowGroup,arrowMap);
						}
					}
					
				} else {
					FlxG.overlap(arrowGroup, Sources, updateFlow);
					FlxG.overlap(arrowGroup, Beams, updateFlow);
				}
				
				
				playerLastX = player.x;
				playerLastY = player.y;
				
				musicIcon.updateMusic(music);
				
				//Updates whether the game is muted or not.
				if (FlxG.mouse.justPressed() && replayIco.overlapsPoint(FlxG.mouse.screenX, FlxG.mouse.screenY)) {
					FlxG.switchState(new TutorialPlayingState(pid, CurrentMap));
				}
			}
		}
		
		/**
		 * Returns cell index that contains this point.
		 */
		public static function getIndexFromPoint(x:int, y:int):int {
			var row:int = (y - Y_OFFSET) / CELL_SIZE;
			var col:int = (x - X_OFFSET) / CELL_SIZE;
			return (row * COLS) + col;
		}
		
		/**
		 * Returns upper left point of cell index.
		 */
		public static function getPointFromIndex(index:int):Point {
			var row:int = index / COLS;
			var col:int = index - (row * COLS); 
			return new Point(col * CELL_SIZE + X_OFFSET, row * CELL_SIZE + Y_OFFSET);
		}
		
		/**
		 * Returns the item located at an index
		 * Assumes each index carries only one item at most (probably bad)
		 */
		private function getItemAtIndex(index:int):Object {
			return tileMap[index];
		}
		
		/**
		 * Given an index, returns the midpoint of that grid cell.
		 */
		private function getMidpointOfIndex(index:int):Point {
			var p:Point = getPointFromIndex(index);
			return new Point(p.x + (CELL_SIZE / 2), p.y + (CELL_SIZE / 2));
		}
		
		/**
		 * If the player loses (If the player collides with a monster).
		 **/
		private function gameOver(obj1:Monster, obj2:FlxObject):void {
			if (player.element == Elemental.VOID) {
				Monsters.remove(obj1);
			} else {
				FlxG.switchState(new TutorialPlayingState(pid, CurrentMap));
			}
		}
		
		/**
		 * Removes the monster and the animal that killed it from the game. Also
		 * adjusts the train.
		 **/
		private function killMonster(monster:Monster, animal:TrainAnimal):void {
			if(MonsterLock) {
				log.logMonsterKilled(monster.x,monster.y)
				MonsterLock = false;
				animal.dead = true;
				train.remove();
				collideTrain.remove(animal);
				Monsters.remove(monster);
			}
		}
		
		/**
		 * Tells the monster what to do if it collides with a wall.
		 **/
		private function monsterAI(monster:Monster, obj1:FlxObject):void {
			monster.changeDirection();
		}
		
		/**
		 * The collision function for animals and walls. If the animal hits a wall
		 * it is set on a path to return to the train. If it collides a second
		 * time while returning it simply is teleported back to the train.
		 * 
		 * Not the greatest, but functional for now. 
		 **/
		private function returnAnimal(obj1:FlxObject, animal:TrainAnimal):void {
			if (animal.returning) {
				animal.returning = false;
				animal.thrown = false;
			} else {
				animal.returning = true;
				animal.stopThrowingAnim();
			}
		}
		
		/**
		 *  collision behavior between a player and an obstacle
		 */
		private function collideObs(obj1:FlxObject, obj2:FlxObject):void{
			var player:Player = obj2 as Player;
			var obs:Obstacle = obj1 as Obstacle;
			
			
			if (!obs.typeMatch(player) && player.element != Elemental.VOID) {
				obs.playElementsAnimation();
				player.x = playerLastX;
				player.y = playerLastY;
			} else {
				obs.playPassableAnimation();
				/*var currentIndex:int = getIndexFromPoint(player.x,player.y);
				var lastIndex:int = getIndexFromPoint(playerLastX,playerLastY);
				var condOne:Boolean = tileMap[lastIndex] != null;
				var condTwo:Boolean = !(tileMap[lastIndex] is Obstacle);
				var condThree:Boolean = 
				
				if ( (tileMap[player.lastIndex] != null) && !(tileMap[player.lastIndex] is Obstacle)  ){
				trace(tileMap[player.lastIndex].toString());
				//log.logObstacleCrossed(player);
				}*/
			}
		}
		
		private function lightSource(theSource:PlumeSource, player:Player):void {
			if (theSource.element == player.element) {
				Sources.add(UnlitSources.remove(theSource, true));
				theSource.turnOn();
			}
		}
		
		/**
		 * If an arrow if placed on an beam or source, check to see
		 * if we should update the flow.
		 */
		private function updateFlow(obj1:FlxObject, obj2:FlxObject):void {
			// Flixel is mean and switches params on you for fun.
			var arr:Arrow;
			var target:FlxObject;
			if (obj1 is Arrow) {
				arr = obj1 as Arrow;
				target = obj2;
			} else {
				arr = obj2 as Arrow;
				target = obj1;
			}
			
			if (arr.isClicked()) {
				return;
			}
			
			if (!Music.MUTE && arrowLastPlaced == getIndexFromPoint(arr.x,arr.y)){
				FlxG.play(Res.onSound);
				arrowLastPlaced = -1;
			}
			
			var direction:int = arr.getOrientation();
			
			if (target is PlumeSource) {
				var source:PlumeSource = target as PlumeSource;
				if (!source.on) {
					arr.setAssociation(source.element);
					drawBeam(source, getIndexFromPoint(source.x, source.y), source.element, direction);
				}
			} else if (target is SourceBeam) {
				var beam:SourceBeam = target as SourceBeam;
				if (!beam.connected) {
					arr.setAssociation(beam.element);
					drawBeam(beam, getIndexFromPoint(arr.x, arr.y), beam.element, direction);
				} 
			}
		}
		
		/**
		 * Transforms player into a new element, if sourceforge is lit.
		 * Called when player walks into a sourceforge.
		 */
		private function transform(forge:SourceForge, p:Player):void {
			if (forge.element != p.element) {
				var playerPos:FlxPoint = p.getMidpoint();
				var fPos:FlxPoint = forge.getMidpoint();
				if (Math.abs(playerPos.x - fPos.x) <= 10 &&
					Math.abs(playerPos.y - fPos.y) <= 10 &&
					forge.isLit()) {
					forge.transformationAnim();
					p.transform(forge.element); 
				}
				if (forge.element == Elemental.VOID){
					music = Res.voidMusic;
				}
			}
		}
		
		/**
		 * Draws the longest beam that can be emitted from the specified source
		 * in the specified direction.
		 * Current version: beams collide only with walls and sourceforges.
		 */
		public function drawBeam(startObj:FlxSprite, startIndex:int, element:int, direction:int):void {
			var index:int = stepToNext(startIndex, direction);
			var item:Object;
			var length:int = 0;
			var forge:SourceForge;
			var theSource:PlumeSource;
			var spawnNew:Boolean = false; // same for starting on arrow.
			
			while (index >= 0) {
				item = getItemAtIndex(index);
				if (item is FlxTileblock && !(item is Obstacle)) {
					break;
				} else if (item is SourceForge) {
					forge = item as SourceForge;
					forge.addSource(element);
					break;			
				} else if (arrowMap[index] is Arrow) {
					var newArr:Arrow = arrowMap[index] as Arrow;
					if (Direction.getOpposite(newArr.getOrientation()) == direction) {
						break;
					}
					if(newArr.isAssociatedWith(element)) {
						break;
					} else {
						//increasing the length by 1 if there's a new arrow
						//solves the problem and overlaps the arrow: Not bad I hope.
						newArr.setAssociation(element);
						spawnNew = true;
						length++;
					}
					break;
				} else if (item is PlumeSource) {
					theSource = item as PlumeSource;
					if (!theSource.isLightOn()) {
						if (theSource.element == element) {
							if (!voidIsOnTheSource(theSource)) {
								theSource.turnOn();
								Sources.add(UnlitSources.remove(theSource, true));
							}
						}
					}
				}
				length++;
				index = stepToNext(index, direction);
			}
			
			if (length > 0 || forge != null) {
				var startsOnArrow:Boolean = arrowMap[startIndex] != null;
				var beam:SourceBeam = new SourceBeam(startIndex, forge, startsOnArrow, spawnNew, element, length, direction);
				Beams.add(beam);
				
				if (startObj is PlumeSource) {
					var start:PlumeSource = startObj as PlumeSource;
					start.setBeam(beam, forge);
				} else if (startObj is SourceBeam) {
					var startBeam:SourceBeam = startObj as SourceBeam;
					startBeam.connected = true;
				}
				
				// Our beam has hit a new arrow, so we need to draw a new beam.
				if (spawnNew) {
					var arr:Arrow = arrowMap[index]
					drawBeam(beam, index, element, arr.getOrientation());
				}
			} 
		}
		
		/**
		 * Removes all beams from screen, resets sources to off.
		 */
		private function clearBeams():void {
			var array:Array = Beams.members;
			var oldLength:int = array.length;
			for (var i:int = 0; i < oldLength; i++) {
				var beam:SourceBeam = array[0] as SourceBeam;
				if (beam != null) {
					beam.disconnect();
				}
				array.shift();
			}
			var cutOff:int = Sources.getFirstNull();
			if (cutOff == -1) {
				cutOff = Sources.length;
			}
			for (var j:int = 0; j < cutOff; j++) {
				var s:PlumeSource = Sources.members[j] as PlumeSource;
				s.removeBeam();
			}
			
			var ArrowArray:Array = arrowGroup.members;
			var theLength:int = ArrowArray.length;
			for (var k:int = 0; k < theLength; k++) {
				if (ArrowArray[k] is Arrow) {
					var arrow:Arrow = ArrowArray[k] as Arrow;
					arrow.clearAssociations();
				}
			}
		}
		
		/**
		 * Returns the neighbor index in the specified direction.
		 * -1 is returned if it's an out-of-bounds cell.
		 */
		private function stepToNext(startIndex:int, direction:int):int {
			var nextIndex:int;
			if (direction == Direction.NORTH) {
				nextIndex = startIndex - COLS;
				if (nextIndex < 0) {
					return -1;
				}
			} else if (direction == Direction.SOUTH) {
				nextIndex = startIndex + COLS;
				if (nextIndex > (COLS * ROWS) - 1) {
					return -1;
				}
			} else if (direction == Direction.EAST) {
				if ((startIndex + 1) % COLS == 0) {
					return -1;
				}
				nextIndex = startIndex + 1;
			} else {
				if ((startIndex ) % COLS == 0) {
					return -1;
				}
				return startIndex - 1;
			}
			return nextIndex;
		}
		
		private function turnOffSource(player:Player, theSource:PlumeSource):void {
			if (theSource.isLightOn()) {
				theSource.turnOff();
				Sources.remove(theSource, true);
				UnlitSources.add(theSource);
				clearBeams();
			}
		}
		
		private function voidIsOnTheSource(theSource:PlumeSource):Boolean {
			if (player.element != Elemental.VOID) {
				return false;
			} else {
				var sMP:FlxPoint = theSource.getMidpoint();
				var pMP:FlxPoint = player.getMidpoint();
				return  (Math.abs(sMP.x - pMP.x) < 33 && Math.abs(sMP.y - pMP.y) < 33);
			}
		} 
		private function displayTutorialAnimation():void {
			if (tutorialAnimations != null) {
				add(tutorialAnimations);
				tutorialAnimations.playAnimation();
				showingTutorial = true;
				Elemental.FREEZE = true;
			}
		}
	}
}